import numpy as np
import matplotlib.pyplot as plt
import datetime as dt
import yfinance as yf
plt.style.use("ggplot")\[
C A G R=\left[\left(\frac{E V}{B V}\right)^{1/n}-1\right]\times 100
\] where: \(E V=\) Ending value
\(B V=\) Beginning value
\(n=\) Number of years
sp500 = yf.download(
["^GSPC"],
start=dt.datetime.today() - dt.timedelta(days=1500),
end=dt.datetime.today(),
progress=True,
actions="inline",
interval="1d",
)[*********************100%***********************] 1 of 1 completed
This function is for daily data, because we use 252 number of trading days in a year to calculate \(n\).
def get_cagr(df):
EV = df["Close"][-1]
BV = df["Close"][0]
n = len(df) / 252
cagr = (EV / BV) ** (1 / n) - 1
print("CAGR: {:.2f}%".format(cagr * 100))
return cagrcagr = get_cagr(sp500)CAGR: 14.62%
/tmp/ipykernel_4295/3211283570.py:2: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
/tmp/ipykernel_4295/3211283570.py:3: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
Volatility
Simple as you have imagined, volatility is commonly measured by standard deviation. The only thing you need to take heed is to know how to convert to annualized volatility. \[ \text{daily return} \times \sqrt{252}\\ \text{weekly return} \times \sqrt{52}\\ \text{monthly return} \times \sqrt{12} \]
def get_volatility(df, freq):
"""
The function is for using daily trading data.
"""
daily_ret = df["Close"].pct_change().std()
if freq == "daily":
vol = daily_ret * np.sqrt(252)
elif freq == "weekly":
vol = daily_ret * np.sqrt(52)
elif freq == "monthly":
vol = daily_ret * np.sqrt(12)
return volget_volatility(sp500, "daily")0.1664305706560202
Max Drawdown and Calmar Ratio
Max drawdown is the percentage counts the maximum drop from peak return in a certain period.
def get_max_dd(df):
df["daily_ret"] = df["Close"].pct_change()
df["cum_ret"] = (1 + df["daily_ret"]).cumprod()
df["cum_trailing_max"] = df["cum_ret"].cummax()
df["drawdown"] = df["cum_trailing_max"] - df["cum_ret"]
df["drawdown_pct"] = df["drawdown"] / df["cum_trailing_max"]
max_dd = df["drawdown_pct"].max()
print("Max drawdown: {:.4f}%".format(max_dd * 100))
return max_ddmdd = get_max_dd(cisco)Max drawdown: 42.8079%
Calmar ratio is similar to Sharpe ratio, but the risk is replace by maximum drawdown. A hedge fund manager named Terry W. Young invented Calmar ratio, and ‘Calmar’ is an acronym of his company’s name and its newsletter: CALifornia Managed Accounts Reports.
def get_calmar(df):
calmar = get_cagr(df) / get_max_dd(df)
print("Calmar ratio: {:.4f}%".format(calmar))
return calmarget_calmar(cisco)CAGR: 7.60%
Max drawdown: 42.8079%
Calmar ratio: 0.1775%
/tmp/ipykernel_4295/3211283570.py:2: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
/tmp/ipykernel_4295/3211283570.py:3: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
0.17752693121492458